Informe Hito 3

Integrantes: Víctor Caro, Víctor Navarro, Javier Gómez

Profesora: Dra. Bárbara Poblete

Auxiliares: Hernán Sarmiento, José Miguel Herrera

Fecha de Entrega: 21-12-2018

Introducción

En el presente documento se expone la totalidad del trabajo realizado para el proyecto semestral del curso CC5206-Introducción a la Minería de Datos.

Para el hito 2, se reformuló el hito 1 considerando los comentarios de la profesora. Se revisitó el problema con una perspectiva distinta, se buscó un objetivo aparte al presentado por la competencia de Kaggle y se escribió el código por cuenta propia en python en vez de ocupar lo presentado en el informe anterior, donde se usó un tutorial en R como pauta a seguir. Cabe destacar que se usó como inspiración lo entregado en la tarea Informe Hito 1.

En el hito 3 se creó un clasificador de barrios a partir de las casas en el dataset, incluido su precio de venta. Para esta tarea se ocupó SVM y Random Forest. Adicionalmente, con el objetivo en mente de crear un recomendador de características relevantes para casas se aplicó Nonnegative Matrix Factorization (NMF) al dataset considerando 45 atributos generalizables dentro de la totalidad de atributos del dataset. El objetivo de aplicar NMF es la clusterización automática de las columnas del dataset en un número de grupos aribitrario, los cuales están determinados por los pesos de las características que lo componen. De esta manera, estos grupos se pueden interpretar como "Tipos de casas", donde un tipo de casa se diferencia de otro en los pesos de las características que lo componen. Así, se creó una matriz H de casas y grupos de casa, donde el valor en una celda i,j es el peso que posee una determinada casa en el grupo i. Luego, una casa pertenece a un grupo si posee un valor en dicha celda superior a un umbral arbitrario, modelando así un comportamiento más realista donde una casa no sólo pertenece exclusivamente a un cluster sino puede pertenecer una agrupación de estos.

Crear un recomendador a partir de esto es complejo, porque no hay manera de determinar qué tipo de casa es mejor para todos, por lo que a partir de los grupos generados se pueden obtener las 10 características más relevantes por grupo de casa, así, si se puede establecer una relación entre un cierto número de tipos de casas y los grupos generados a partir de NMF, podría uno escoger una casa, determinar que pertenece a un cierto grupo y a partir de esto escoger las 10 características más relevantes en las que uno debería fijarse al momento de vender.

Motivación

La valuación de un bien inmueble es el proceso de desarrollar una opinión de valor para el bien en cuestión. La transacción de un bien inmueble requiere una tasación previa para determinar su valor. Múltiples factores influyen en la valoración final de un bien, encontrando entre estos su vecindario, tamaño, materiales usados para su construcción, cantidad de habitaciones, remodelaciones, etcétera. Es de interés para cualquier persona que desee participar en alguna transacción de bienes inmuebles tener una buena tasación previa del bien a transaccionar.

Nuestra propuesta de proyecto semestral para el curso CC5206 - Introducción a la Minería de Datos consiste en analizar el Ames Housing dataset, compilado por Dean De Cock y publicado en Kaggle 2 como una competencia para principiantes. Este dataset contiene registros de casas vendidas entre 2006 y 2010 en Ames, Iowa, con una serie de características y su valoración en USD.

De esta manera, crear un predictor del valor de una porpiedad, permitiría ahorrar el trabajo del tasador. Otro punto de estos datos es que permite analizar qué atributos influyen más o menos, si la fecha de venta influye o no, la estación del año, etc.

Problemática

El objetivo principal de nuestro proyecto es predecir el precio final de una casa a partir de sus características. Este trabajo actualmente está a cargo de un tasador y nos gustaría explorar qué tan cerca de los precios finales de un tasador están las predicciones de algoritmos aplicados a nuestros datos. A raíz de esto surgen una serie de preguntas tales como ¿Cuál es la distribución de precios de venta de las casas en el dataset?¿Cómo es la condición general de las casas vendidas?¿Existen características de una casa que no influyen en el precio de esta?, naturalmente también, ¿Qué características son las más relevantes al tasar una casa?, ¿Qué características de una casa poseen una alta o baja correlación?¿Qué parámetros poseen una baja o alta varianza?¿Cuántas casas han sido remodeladas?¿Con qué parámetros está positivamente correlacionado el precio de venta?¿Y negativamente? Abordamos estas preguntas y otras en el análisis exploratorio a continuación.

Al mismo tiempo, creemos que podría existir interés por entes gubernamentales o privados, los cuales podrían estar interesados en estudiar segmentos de alguna ciudad en particular, o predecir precios de venta de proyectos inmobiliarios. Dada la naturaleza del entrenamiento de los algoritmos, sería necesario realizar un entrenamiento distinto por cada ciudad, puesto que las relaciones existentes en los barrios de una localidad no necesariamente son las mismas o si quiera similares a las de otra. Por lo tanto, buscamos determinar qué factores son generalizables, vale decir, qué características de una casa tienden a ser determinantes al momento de establecer un precio de venta. De esta manera, creemos que nuestro proyecto podría ser relevante para tasadores u otros expertos que deseen buscar apoyo en herramientas computacionales para la realización de su labor.

Análisis Exploratorio

In [0]:
%matplotlib inline
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="darkgrid")
from sklearn import metrics, cross_validation
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import mean_squared_error, explained_variance_score
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingRegressor, RandomForestRegressor
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import Imputer
df = pd.read_csv("/content/gdrive/My Drive/train.csv", index_col = 0)
print(df.shape)
df.head()
/usr/local/lib/python3.6/dist-packages/sklearn/cross_validation.py:41: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
  "This module will be removed in 0.20.", DeprecationWarning)
(1460, 80)
Out[0]:
MSSubClass MSZoning LotFrontage LotArea Street Alley LotShape LandContour Utilities LotConfig ... PoolArea PoolQC Fence MiscFeature MiscVal MoSold YrSold SaleType SaleCondition SalePrice
Id
1 60 RL 65.0 8450 Pave NaN Reg Lvl AllPub Inside ... 0 NaN NaN NaN 0 2 2008 WD Normal 208500
2 20 RL 80.0 9600 Pave NaN Reg Lvl AllPub FR2 ... 0 NaN NaN NaN 0 5 2007 WD Normal 181500
3 60 RL 68.0 11250 Pave NaN IR1 Lvl AllPub Inside ... 0 NaN NaN NaN 0 9 2008 WD Normal 223500
4 70 RL 60.0 9550 Pave NaN IR1 Lvl AllPub Corner ... 0 NaN NaN NaN 0 2 2006 WD Abnorml 140000
5 60 RL 84.0 14260 Pave NaN IR1 Lvl AllPub FR2 ... 0 NaN NaN NaN 0 12 2008 WD Normal 250000

5 rows × 80 columns

Vemos que nuestro dataset contiene 1460 filas y 80 columnas, las que corresponden a diversos atributos de las casas. Para una explicación detallada de cada uno de estos, referirse al documento adjunto "data_description.txt"

En vista de que existen columnas con NaNs, buscamos las columnas que tengan más de 200 valores NaN y hacemos un drop en el dataframe. Notar que si mostramos la variable 'nan_por_columnas', las que cumplen con esta condición son las primeras 6.

In [0]:
nan_por_columnas = df.isna().sum().sort_values(axis=0, ascending=False)
top_6 = nan_por_columnas[:6]
df = df.drop(columns=top_6.index.tolist())
print(top_6)
df.shape
PoolQC         1453
MiscFeature    1406
Alley          1369
Fence          1179
FireplaceQu     690
LotFrontage     259
dtype: int64
Out[0]:
(1460, 74)

Se puede observar que las columnas con mayor cantidad de valores NaNs son:

  • PoolQC: Calidad de la Piscina
  • MiscFeature: Misceláneos no cubiertos por otras características
  • Alley: Indica el tipo de acceso a callejón
  • Fence: Calidad de la cerca
  • FirePlaceQu: Calidad de la estufa/chimenea

Con esto podemos ver que la mayor parte de las casas no poseen piscina, características misceláneas (como ascensores o cancha de tenis), callejón, cerca ni chimeneas.

Características de las casas

Podemos estudiar la distribución de casas por barrio usando la columna Neighborhood. Notar que los nombres están acotados.

In [0]:
plt.figure(figsize=(8,8))
plt.title('Cantidad de propiedades por barrio', size=20)
sns.countplot(y='Neighborhood', data=df)
plt.xlabel('Cantidad de propiedades')
plt.show()
/usr/local/lib/python3.6/dist-packages/seaborn/categorical.py:1428: FutureWarning: remove_na is deprecated and is a private function. Do not use.
  stat_data = remove_na(group_data)

Hay dos barrios que concentran una mayor cantidad de propiedades, que son North Ames (NAmes) y College Creek (CollgCr).

Ahora consideramos la columna MSZoning, la cual identifica la clasificación general de zonificación de la propiedad. Los códigos utilizados corresponden a zonas Comerciales (C), Pueblo Flotante Residencial(FV), Residencial de Alta Densidad (RH), Residencial de baja densidad (RL) y residencial de mediana densidad (RM).

In [0]:
plt.figure(figsize=(8,8))
plt.title('Distribución de casas por Zonificación', size=20)
sns.countplot(y='MSZoning', data=df)
plt.ylabel('Categorías de Zonificación')
plt.xlabel('Cantidad de propiedades')
plt.show()
/usr/local/lib/python3.6/dist-packages/seaborn/categorical.py:1428: FutureWarning: remove_na is deprecated and is a private function. Do not use.
  stat_data = remove_na(group_data)

Vemos que la mayor parte de las casas están en áreas residenciales de baja densidad, seguidas por mediana densidad.

In [0]:
plt.figure(figsize=(15,8))
sns.boxplot(x="Neighborhood", y="SalePrice", data=df)
plt.xticks(rotation=45)
/usr/local/lib/python3.6/dist-packages/seaborn/categorical.py:454: FutureWarning: remove_na is deprecated and is a private function. Do not use.
  box_data = remove_na(group_data)
Out[0]:
(array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
        17, 18, 19, 20, 21, 22, 23, 24]),
 <a list of 25 Text xticklabel objects>)

Podemos estudiar el precio de venta de las casas:

In [0]:
df['SalePrice'].describe()
Out[0]:
count      1460.000000
mean     180921.195890
std       79442.502883
min       34900.000000
25%      129975.000000
50%      163000.000000
75%      214000.000000
max      755000.000000
Name: SalePrice, dtype: float64
In [0]:
sns.distplot(df['SalePrice'], bins=50, kde=False)
Out[0]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08e6964908>

Correlación

Dada la alta cantidad de carácterísticas, para graficar la matriz de correlación consideramos solo aquellas que tengan una correlación superior a 0.4 (sea positiva o negativa):

In [0]:
c = df.corr()
m = (c.mask(np.eye(len(c),dtype=bool)).abs()>0.4).any()
corr = c.loc[m,m]
f, ax = plt.subplots(figsize=(10,8))
sns.heatmap(corr, square=True, ax=ax)
Out[0]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08e6af9e80>

Podemos sacar las 6 características más correlacionadas:

In [0]:
c = corr.abs().unstack().sort_values(ascending=False)
c = c[corr.shape[0]:corr.shape[0]+6]
top_6 = []
for i in range(0,6,2):
  top_6.append(c.keys()[i][0])
  top_6.append(c.keys()[i][1])
print(c)
print(top_6)
GarageCars    GarageArea      0.882475
GarageArea    GarageCars      0.882475
YearBuilt     GarageYrBlt     0.825667
GarageYrBlt   YearBuilt       0.825667
GrLivArea     TotRmsAbvGrd    0.825489
TotRmsAbvGrd  GrLivArea       0.825489
dtype: float64
['GarageCars', 'GarageArea', 'YearBuilt', 'GarageYrBlt', 'GrLivArea', 'TotRmsAbvGrd']

Podemos entonces graficar sus densidades:

In [0]:
f = plt.figure(figsize =(15,12))
plt.suptitle('Top 6 Correlaciones', fontsize=16)
for i in range(0,6):
  feature = top_6[i]
  ax = f.add_subplot(3,3,i+1)
  sns.distplot(df[feature].fillna(df[feature].mean()), label=feature)
  plt.legend()

plt.show() 

Experimentos y Resultados Preliminares

Anteriormente se presentó el trabajo correspondiente al hito 1. A continuación se presentan los experimentos realizados y resultados preliminares obtenidos hasta el momento.

Como se puede observar, hay columnas dentro del dataset que contienen strings. $\texttt{scikit-learn}$ no trabaja con strings, por lo que codeamos dichas columnas con un entero para cada string usando LabelEncoder():

In [0]:
#Guardamos la lista de barrios
barrios = df['Neighborhood'].value_counts().keys().tolist()

featureList = np.array(df.columns).tolist()[0:df.shape[1]-1]
for feature in featureList:
    if df[feature].dtype == 'object':
        df[feature] = LabelEncoder().fit_transform(df[feature].tolist())
df.head()
Out[0]:
MSSubClass MSZoning LotArea Street LotShape LandContour Utilities LotConfig LandSlope Neighborhood ... EnclosedPorch 3SsnPorch ScreenPorch PoolArea MiscVal MoSold YrSold SaleType SaleCondition SalePrice
Id
1 60 3 8450 1 3 3 0 4 0 5 ... 0 0 0 0 0 2 2008 8 4 208500
2 20 3 9600 1 3 3 0 2 0 24 ... 0 0 0 0 0 5 2007 8 4 181500
3 60 3 11250 1 0 3 0 4 0 5 ... 0 0 0 0 0 9 2008 8 4 223500
4 70 3 9550 1 0 3 0 0 0 6 ... 272 0 0 0 0 2 2006 8 0 140000
5 60 3 14260 1 0 3 0 2 0 15 ... 0 0 0 0 0 12 2008 8 4 250000

5 rows × 74 columns

De acuerdo a la distribución de precios, determinamos que las viviendas con precios superiorer a los 500000 USD son considerados outliers. Por lo tanto son eliminados del dataset:

In [0]:
print('Dimensiones antes de sacar outliers:',df.shape)
df_drop = df.drop(df[df['SalePrice']>500000].index)
print('Dimensiones después de sacar outliers:',df_drop.shape)
Dimensiones antes de sacar outliers: (1460, 74)
Dimensiones después de sacar outliers: (1451, 74)

Otra cosa a mencionar es que los datos NaN deben tener valores finitos. Para evitar overfitting se prefiere dejar dichos valores como el promedio. Para ello se ocupa Imputer():

In [0]:
imp = Imputer(missing_values=np.nan, strategy='mean')
df_drop[featureList] = imp.fit_transform(df_drop[featureList])

Escalamos todos los datos usando RobustScaler, el cual usa métodos robustos en caso de outliers en las columnas:

In [0]:
from sklearn.preprocessing import RobustScaler
df_drop[featureList] = RobustScaler().fit_transform(df_drop[featureList])

Separación por barrio

Para predecir los precios por barrio separamos el dataset:

In [0]:
df_barrios = {}
barrios_num = df_drop['Neighborhood'].value_counts().keys()
for i in range(0,len(barrios_num)):
  df_barrios[barrios[i]] = df_drop[df_drop['Neighborhood'] == barrios_num[i]]
  df_barrios[barrios[i]] = df_barrios[barrios[i]].drop(columns=['Neighborhood'])

print('Cantidad de barrios:',len(df_barrios.keys()))
Cantidad de barrios: 25

Descartamos los barrios con menos de 90 viviendas:

In [0]:
for key, df in list(df_barrios.items()):
  if(df.shape[0]<=90):
    df_barrios.pop(key,None)
    
print('Cantidad de barrios:',len(df_barrios.keys()))
Cantidad de barrios: 4

Primera regresión: Gradient Boosting Regression

GradientBoostingRegressor es una función de $\texttt{scikit-learn}$, corresponde un modelo por etapas, el cual permite optimizar múltiples loss functions usando un árbol de decisión con una regresión por cada etapa.

Posee 3 importantes parámetros: n_estimator, max_depth, min_samples_split. A priori desconocemos cual combinación de valores es la mejor para obtener el mejor score, por lo que procedemos a usar el método GridSearchCV. Dicho método recibe un clasificador y un diccionario cuyas llaves corresponden a los argumentos del clasificador, cada llave posee un array de posibles valores. GridSearchCV se encarga de obtener la combinación de argumentos que entregan el mejor score para el clasificador.

In [0]:
for key, dfs in df_barrios.items():
  print("############## Regresión para", key, "###############")
  print("Dimensiones:", dfs.shape)
  X = dfs[dfs.columns[:-1]]
  y = dfs[dfs.columns[-1]]
  clf = GradientBoostingRegressor()
  param_grid = {'n_estimators': [100, 200, 300, 400, 500, 600, 700], 'max_depth': [1, 2, 3, 4, 5, 6, 7, 8, 9],
                'min_samples_split': [2, 3 ,4 ,5 , 6]}
  #n_jobs corresponde a la cantidad de threads a ocupar, cv corresponde a los k-folds a ocupar en la cross-validation
  clf_grid = GridSearchCV(clf, param_grid, verbose=1, n_jobs=-1, cv=3)
  clf_grid.fit(X, y)

  print("Mejores parametros GBR:", clf_grid.best_params_)
  print("Score CV: ", clf_grid.best_score_)
############## Regresión para NAmes ###############
Dimensiones: (225, 73)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done 162 tasks      | elapsed:   15.5s
[Parallel(n_jobs=-1)]: Done 312 tasks      | elapsed:   39.0s
[Parallel(n_jobs=-1)]: Done 562 tasks      | elapsed:  1.8min
[Parallel(n_jobs=-1)]: Done 912 tasks      | elapsed:  3.6min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed:  3.8min finished
Mejores parametros GBR: {'max_depth': 1, 'min_samples_split': 4, 'n_estimators': 700}
Score CV:  0.725855823641544
############## Regresión para CollgCr ###############
Dimensiones: (150, 73)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done 205 tasks      | elapsed:   17.1s
[Parallel(n_jobs=-1)]: Done 449 tasks      | elapsed:   53.4s
[Parallel(n_jobs=-1)]: Done 699 tasks      | elapsed:  1.7min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed:  2.5min finished
Mejores parametros GBR: {'max_depth': 1, 'min_samples_split': 3, 'n_estimators': 500}
Score CV:  0.8983117324565015
############## Regresión para OldTown ###############
Dimensiones: (113, 73)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done 180 tasks      | elapsed:   13.9s
[Parallel(n_jobs=-1)]: Done 480 tasks      | elapsed:   54.3s
[Parallel(n_jobs=-1)]: Done 942 out of 945 | elapsed:  2.1min remaining:    0.4s
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed:  2.1min finished
Mejores parametros GBR: {'max_depth': 3, 'min_samples_split': 2, 'n_estimators': 200}
Score CV:  0.722272989114793
############## Regresión para Edwards ###############
Dimensiones: (100, 73)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done 232 tasks      | elapsed:   18.8s
[Parallel(n_jobs=-1)]: Done 588 tasks      | elapsed:  1.1min
[Parallel(n_jobs=-1)]: Done 942 out of 945 | elapsed:  2.0min remaining:    0.4s
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed:  2.0min finished
Mejores parametros GBR: {'max_depth': 4, 'min_samples_split': 4, 'n_estimators': 700}
Score CV:  0.5698057827832154

Segunda regresión: Random Forest Regression

Resumidamente un random forest corresponde a un conjunto de árboles de decisión donde cada árbol recibe un subconjunto de las features disponibles del dataset (distinto para cada árbol) para entrenar. El fin de esto es no caer en mínimos locales al entrenar. El algoritmo al final entrega la moda en los resultados que entregó cada árbol.

In [0]:
for key, dfs in df_barrios.items():
  print("############## Regresión para", key, "###############")
  print("Dimensiones:", dfs.shape)
  X = dfs[dfs.columns[:-1]]
  y = dfs[dfs.columns[-1]]
  clf = RandomForestRegressor()
  param_grid = {'n_estimators': [100, 200, 300, 400, 500, 600, 700], 'max_depth': [1, 2, 3, 4, 5, 6, 7, 8, 9],
                'min_samples_split': [2, 3 ,4 ,5 , 6]}
  #n_jobs corresponde a la cantidad de threads a ocupar, cv corresponde a los k-folds a ocupar en la cross-validation
  clf_grid = GridSearchCV(clf, param_grid, verbose=1, n_jobs=-1, cv=3)
  clf_grid.fit(X, y)

  print("Mejores parametros GBR:", clf_grid.best_params_)
  print("Score CV: ", clf_grid.best_score_)
############## Regresión para NAmes ###############
Dimensiones: (225, 73)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done  46 tasks      | elapsed:   18.7s
[Parallel(n_jobs=-1)]: Done 196 tasks      | elapsed:  1.4min
[Parallel(n_jobs=-1)]: Done 446 tasks      | elapsed:  3.7min
[Parallel(n_jobs=-1)]: Done 796 tasks      | elapsed:  7.7min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed:  9.5min finished
Mejores parametros GBR: {'max_depth': 6, 'min_samples_split': 2, 'n_estimators': 100}
Score CV:  0.6750492341030938
############## Regresión para CollgCr ###############
Dimensiones: (150, 73)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done  46 tasks      | elapsed:   17.9s
[Parallel(n_jobs=-1)]: Done 196 tasks      | elapsed:  1.4min
[Parallel(n_jobs=-1)]: Done 446 tasks      | elapsed:  3.4min
[Parallel(n_jobs=-1)]: Done 796 tasks      | elapsed:  6.7min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed:  8.2min finished
Mejores parametros GBR: {'max_depth': 9, 'min_samples_split': 2, 'n_estimators': 300}
Score CV:  0.8566918153528676
############## Regresión para OldTown ###############
Dimensiones: (113, 73)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done  46 tasks      | elapsed:   17.9s
[Parallel(n_jobs=-1)]: Done 196 tasks      | elapsed:  1.4min
[Parallel(n_jobs=-1)]: Done 446 tasks      | elapsed:  3.4min
[Parallel(n_jobs=-1)]: Done 796 tasks      | elapsed:  6.6min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed:  8.0min finished
Mejores parametros GBR: {'max_depth': 6, 'min_samples_split': 2, 'n_estimators': 100}
Score CV:  0.636642886358957
############## Regresión para Edwards ###############
Dimensiones: (100, 73)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done  46 tasks      | elapsed:   17.6s
[Parallel(n_jobs=-1)]: Done 196 tasks      | elapsed:  1.3min
[Parallel(n_jobs=-1)]: Done 446 tasks      | elapsed:  3.3min
[Parallel(n_jobs=-1)]: Done 796 tasks      | elapsed:  6.4min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed:  7.7min finished
Mejores parametros GBR: {'max_depth': 8, 'min_samples_split': 3, 'n_estimators': 100}
Score CV:  0.5236276470917124

Regresión para precio en General (Todo el dataSet)

In [0]:
X = df_drop[df_drop.columns[:-1]]
y = df_drop[df_drop.columns[-1]]

Gradient Boosting

In [0]:
clf = GradientBoostingRegressor()
param_grid = {'n_estimators': [100, 200, 300, 400, 500, 600, 700], 'max_depth': [1, 2, 3, 4, 5, 6, 7, 8, 9],
              'min_samples_split': [2, 3 ,4 ,5 , 6]}
#n_jobs corresponde a la cantidad de threads a ocupar, cv corresponde a los k-folds a ocupar en la cross-validation
clf_grid = GridSearchCV(clf, param_grid, verbose=1, n_jobs=-1, cv=3)
clf_grid.fit(X, y)

print("Mejores parametros GBR:", clf_grid.best_params_)
print("Score CV: ", clf_grid.best_score_)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done  73 tasks      | elapsed:   15.5s
[Parallel(n_jobs=-1)]: Done 223 tasks      | elapsed:  1.3min
[Parallel(n_jobs=-1)]: Done 473 tasks      | elapsed:  5.9min
[Parallel(n_jobs=-1)]: Done 823 tasks      | elapsed: 20.7min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed: 29.0min finished
Mejores parametros GBR: {'max_depth': 2, 'min_samples_split': 2, 'n_estimators': 500}
Score CV:  0.9034742350852354

RandomForest

In [0]:
clf = RandomForestRegressor()
param_grid = {'n_estimators': [100, 200, 300, 400, 500, 600, 700], 'max_depth': [1, 2, 3, 4, 5, 6, 7, 8, 9],
              'min_samples_split': [2, 3 ,4 ,5 , 6]}
#n_jobs corresponde a la cantidad de threads a ocupar, cv corresponde a los k-folds a ocupar en la cross-validation
clf_grid = GridSearchCV(clf, param_grid, verbose=1, n_jobs=-1, cv=3)
clf_grid.fit(X, y)

print("Mejores parametros RFR:", clf_grid.best_params_)
print("Score CV: ", clf_grid.best_score_)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done  46 tasks      | elapsed:   29.7s
[Parallel(n_jobs=-1)]: Done 196 tasks      | elapsed:  2.6min
[Parallel(n_jobs=-1)]: Done 446 tasks      | elapsed:  8.5min
[Parallel(n_jobs=-1)]: Done 796 tasks      | elapsed: 22.0min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed: 29.3min finished
Mejores parametros GBR: {'max_depth': 9, 'min_samples_split': 2, 'n_estimators': 300}
Score CV:  0.8698160724407039
In [0]:
#Mejores parametros GradientBoosting: {'max_depth': 2, 'min_samples_split': 2, 'n_estimators': 500}
#Mejores parametros RandomForest: {'max_depth': 9, 'min_samples_split': 2, 'n_estimators': 300}"

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)

clf = RandomForestRegressor(n_estimators=clf_grid.best_params_['n_estimators'], 
                                max_depth=clf_grid.best_params_['max_depth'],
                                min_samples_split=clf_grid.best_params_['min_samples_split'])

clf.fit(X_train, y_train)
print("Score: ",clf.score(X_test,y_test))
y_pred = clf.predict(X_test)
print("Explained Variance Score:", explained_variance_score(y_test, y_pred)) #mientras mas cercano a 1 mejor
print("MSE: ", mean_squared_error(y_test, y_pred)) #mientras mas cercano a 0 mejor
Score:  0.8796613125689488
Explained Variance Score: 0.8806647101004261
MSE:  587392862.7686098

Se quitan feautures segun importancia

In [0]:
feature_importance = clf.feature_importances_
# importancia relativa a la caracteristica más importante
feature_importance = 100.0 * (feature_importance / feature_importance.max())
feature_score = {}
df_drop2=df_drop
for i in range(0,len(feature_importance)):
  if(feature_importance[i]<0.3):  #Saco las de bajo el 0.3%
    df_drop2=df_drop2.drop(columns=featureList[i])
    
df_drop2.shape
  
Out[0]:
(1451, 34)
In [0]:
X = df_drop2[df_drop2.columns[:-1]]
y = df_drop2[df_drop2.columns[-1]]

clf = GradientBoostingRegressor()
param_grid = {'n_estimators': [100, 200, 300, 400, 500, 600, 700], 'max_depth': [1, 2, 3, 4, 5, 6, 7, 8, 9],
              'min_samples_split': [2, 3 ,4 ,5 , 6]}
#n_jobs corresponde a la cantidad de threads a ocupar, cv corresponde a los k-folds a ocupar en la cross-validation
clf_grid = GridSearchCV(clf, param_grid, verbose=1, n_jobs=-1, cv=3)
clf_grid.fit(X, y)

print("Mejores parametros GBR:", clf_grid.best_params_)
print("Score CV: ", clf_grid.best_score_)

clf = RandomForestRegressor()
param_grid = {'n_estimators': [100, 200, 300, 400, 500, 600, 700], 'max_depth': [1, 2, 3, 4, 5, 6, 7, 8, 9],
              'min_samples_split': [2, 3 ,4 ,5 , 6]}
#n_jobs corresponde a la cantidad de threads a ocupar, cv corresponde a los k-folds a ocupar en la cross-validation
clf_grid = GridSearchCV(clf, param_grid, verbose=1, n_jobs=-1, cv=3)
clf_grid.fit(X, y)

print("Mejores parametros RFR:", clf_grid.best_params_)
print("Score CV: ", clf_grid.best_score_)
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done  88 tasks      | elapsed:   15.0s
[Parallel(n_jobs=-1)]: Done 257 tasks      | elapsed:  1.2min
[Parallel(n_jobs=-1)]: Done 507 tasks      | elapsed:  4.5min
[Parallel(n_jobs=-1)]: Done 857 tasks      | elapsed: 14.3min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed: 17.9min finished
Mejores parametros GBR: {'max_depth': 2, 'min_samples_split': 2, 'n_estimators': 400}
Score CV:  0.896977528624947
Fitting 3 folds for each of 315 candidates, totalling 945 fits
[Parallel(n_jobs=-1)]: Done  46 tasks      | elapsed:   26.7s
[Parallel(n_jobs=-1)]: Done 196 tasks      | elapsed:  2.2min
[Parallel(n_jobs=-1)]: Done 446 tasks      | elapsed:  7.0min
[Parallel(n_jobs=-1)]: Done 796 tasks      | elapsed: 17.5min
[Parallel(n_jobs=-1)]: Done 945 out of 945 | elapsed: 23.1min finished
Mejores parametros RFR: {'max_depth': 9, 'min_samples_split': 4, 'n_estimators': 300}
Score CV:  0.8681637642297206

Desarrollo Hito 3

Como fué mencionado en la introducción, el hito 3 tiene como medular la predicción de barrios a partir de sus características y la clusterización de casas en tipos de casas, para luego extraer las características más relevantes por cada grupo generado.

In [1]:
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, train_test_split, cross_val_predict
from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix



def print_confusion_matrix(cm, class_names, title="", figsize = (10,7), fontsize=14):
    cm = cm / cm.astype('float').sum(axis=1)
    df_cm = pd.DataFrame(cm, index=class_names, columns=class_names,)
    fig = plt.figure(figsize=figsize)
    heatmap = sns.heatmap(df_cm, annot=True, cmap=plt.cm.Blues)
    heatmap.yaxis.set_ticklabels(heatmap.yaxis.get_ticklabels(), rotation=0, ha='right', fontsize=fontsize)
    heatmap.xaxis.set_ticklabels(heatmap.xaxis.get_ticklabels(), rotation=45, ha='right', fontsize=fontsize)
    plt.title(title)
    plt.ylabel('True label')
    plt.xlabel('Predicted label')
    return fig

df = pd.read_csv("train2.csv", index_col = 0)
print("dimensiones del dataset:",df.shape)

#Guardamos los barrios
barrios = df['Neighborhood']

df = df.drop(columns=["Neighborhood"])

featureList = np.array(df.columns).tolist()
# Codeamos todos los strings salvo los barrios
for feature in featureList:
    if df[feature].dtype == 'object':
        df[feature] = LabelEncoder().fit_transform(df[feature].tolist())  
# Llenamos los NaN con la media
imp = Imputer(missing_values=np.nan, strategy='mean')
df[featureList] = imp.fit_transform(df[featureList])
# Escalamos
df[featureList] = RobustScaler().fit_transform(df[featureList])
#Recuperamos los barrios
df["Neighborhood"] = barrios
print(df.shape)
df.head()
(1460, 74)
dimensiones del dataset: (1460, 74)
C:\Users\King\Anaconda3\lib\site-packages\sklearn\utils\deprecation.py:58: DeprecationWarning: Class Imputer is deprecated; Imputer was deprecated in version 0.20 and will be removed in 0.22. Import impute.SimpleImputer from sklearn instead.
  warnings.warn(msg, category=DeprecationWarning)
Out[1]:
MSSubClass MSZoning LotArea Street LotShape LandContour Utilities LotConfig LandSlope Condition1 ... 3SsnPorch ScreenPorch PoolArea MiscVal MoSold YrSold SaleType SaleCondition SalePrice Neighborhood
Id
1 0.2 0.0 -0.254076 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 -1.333333 0.0 0.0 0.0 0.541506 CollgCr
2 -0.6 0.0 0.030015 0.0 0.0 0.0 0.0 -1.0 0.0 -1.0 ... 0.0 0.0 0.0 0.0 -0.333333 -0.5 0.0 0.0 0.220173 Veenker
3 0.2 0.0 0.437624 0.0 -1.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 1.000000 0.0 0.0 0.0 0.720024 CollgCr
4 0.4 0.0 0.017663 0.0 -1.0 0.0 0.0 -2.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 -1.333333 -1.0 0.0 -4.0 -0.273728 Crawfor
5 0.2 0.0 1.181201 0.0 -1.0 0.0 0.0 -1.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 2.000000 0.0 0.0 0.0 1.035406 NoRidge

5 rows × 74 columns

Para la clasificación se ocuparán los barrios con un mínimo de 50 viviendas. A continuación se muestran la cantidad de viviendas por barrio.

In [2]:
barrios.value_counts()
Out[2]:
NAmes      225
CollgCr    150
OldTown    113
Edwards    100
Somerst     86
Gilbert     79
NridgHt     77
Sawyer      74
NWAmes      73
SawyerW     59
BrkSide     58
Crawfor     51
Mitchel     49
NoRidge     41
Timber      38
IDOTRR      37
ClearCr     28
SWISU       25
StoneBr     25
Blmngtn     17
MeadowV     17
BrDale      16
Veenker     11
NPkVill      9
Blueste      2
Name: Neighborhood, dtype: int64
In [3]:
barriosOK = [] # barrios que quedan dentro del clasificador
df2 = df
for barrio, v in barrios.value_counts().iteritems():
    # Si la cantidad de casas es menor a 50 se descarta
    if v < 50:
        df2 = df2[df2["Neighborhood"] != barrio]
    else:
        barriosOK.append(barrio)

Se hace un oversampling de todos los barrios que no alcanzan las 225 viviendas que tiene NAmes, para ello se ocupa la técnica SMOTE que crea datos sintéticos a partir de una muestra usando $\texttt{K-Neighbors}$ con en este caso $k=5$. De esta forma todos los barrios tendrán la misma cantidad de viviendas para hacer la clasificación.

In [4]:
X = df2[df2.columns[:-1]]
y = df2[df2.columns[-1]]
# SMOTE
from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=42)
X, y = sm.fit_resample(X, y)

Random Forest

Se hace un GridSearchCV en busca de los mejores parámetros para un Random Forest Classifier

In [ ]:
cv = StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=42)
param_gridRFC = {'n_estimators': np.arange(200,1001,100), 'max_depth': np.arange(15,len(featureList))}
clf_gridRFC = GridSearchCV(RandomForestClassifier(random_state=42), param_gridRFC, verbose=1, n_jobs=-1, cv=cv)
clf_gridRFC.fit(X, y)

print("Mejores parametros RFC: ", clf_gridRFC.best_params_)
print("Score: ", clf_gridRFC.best_score_)

La búsqueda de los mejores parámetros arroja que con $\texttt{max_depth = 18}$ y $\texttt{n_estimators = 600}$ se consigue un score de 0.9077. Pasamos entonces a entrenar Random Forest con un training set del 70%:

In [5]:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.3, random_state=20, stratify=y)

rfc = RandomForestClassifier(n_estimators=600, max_depth=18, random_state=42)
rfc.fit(X_train, y_train)

y_predRFC = rfc.predict(X_test)
print("Accuracy en test set:", accuracy_score(y_test, y_predRFC))

print("Reporte de Clasificacion:")
print(classification_report(y_test, y_predRFC))
Accuracy en test set: 0.8975308641975308
Reporte de Clasificacion:
              precision    recall  f1-score   support

     BrkSide       0.99      0.99      0.99        67
     CollgCr       0.86      0.85      0.86        67
     Crawfor       0.94      0.96      0.95        67
     Edwards       0.88      0.78      0.83        68
     Gilbert       0.97      0.94      0.96        68
       NAmes       0.70      0.73      0.72        67
      NWAmes       0.97      0.96      0.96        68
     NridgHt       0.87      0.93      0.90        67
     OldTown       0.95      0.91      0.93        67
      Sawyer       0.79      0.87      0.83        68
     SawyerW       0.95      0.91      0.93        68
     Somerst       0.92      0.96      0.94        68

   micro avg       0.90      0.90      0.90       810
   macro avg       0.90      0.90      0.90       810
weighted avg       0.90      0.90      0.90       810

De esto podemos sacar la importancia que le dió Random Forest a cada característica. Sacamos entonces todas las características con una importancia relativa a la característica más importante menor al 5%:

In [6]:
X = df2[df2.columns[:-1]]
y = df2[df2.columns[-1]]
feature_importance = rfc.feature_importances_
# importancia relativa a la caracteristica más importante
feature_importance = 100.0 * (feature_importance / feature_importance.max())
X2=X
for i in range(0,len(feature_importance)):
  if(feature_importance[i]<5):  #Saco las de bajo el 5%
    X2=X2.drop(columns=featureList[i])
    
print("Nuevas dimensiones del dataset:", X2.shape)
Nuevas dimensiones del dataset: (1145, 50)

Al tener menos características en el dataset se vería afectado el Score y el tiempo de ejecución de los métodos. Por lo que ejecutamos Random Forest nuevamente aplicandole el algorítmo SMOTE al dataset:

In [7]:
X_train, X_test, y_train, y_test = train_test_split(X2, y, test_size=.3, random_state=20, stratify=y)

#SMOTE
sm = SMOTE(random_state=42)
X_train, y_train = sm.fit_resample(X_train, y_train)
X2 = X_train
y = y_train
X2, y = sm.fit_resample(X2, y)
X2.shape
Out[7]:
(1884, 50)
In [ ]:
# Tarda bastante, no correr
cv = StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=42)
param_gridRFC = {'n_estimators': np.arange(200,1001,100), 'max_depth': np.arange(15,len(featureList))}

clf_gridRFC = GridSearchCV(RandomForestClassifier(random_state=42), param_gridRFC, verbose=1, n_jobs=-1, cv=cv)
clf_gridRFC.fit(X2, y)

print("Mejores parametros RFC: ", clf_gridRFC.best_params_)
print("Score: ", clf_gridRFC.best_score_)

Esta búsqueda de los mejores parámetros arroja que con $\texttt{max_depth = 21}$ y $\texttt{n_estimators = 600}$ se consigue un score de 0.9059, por lo que la disminución de características no afectó mucho el Score. Pasamos entonces a entrenar Random Forest con un training set del 70% y obtenemos su matriz de confusión:

In [8]:
X_train, X_test, y_train, y_test = train_test_split(X2, y, test_size=.3, random_state=20, stratify=y)

rfc = RandomForestClassifier(n_estimators=600, max_depth=21, random_state=42)
rfc.fit(X_train, y_train)

y_predRFC = rfc.predict(X_test)

conf_matrixRFC = confusion_matrix(y_test, y_predRFC)
print("Accuracy en test set:", accuracy_score(y_test, y_predRFC))

print("Reporte de Clasificacion:")
print(classification_report(y_test, y_predRFC))

classNames = barriosOK
f = print_confusion_matrix(conf_matrixRFC, classNames,  title='Confusion Matrix Random Forest')
plt.show()
Accuracy en test set: 0.9081272084805654
Reporte de Clasificacion:
              precision    recall  f1-score   support

     BrkSide       0.96      0.96      0.96        47
     CollgCr       0.93      0.85      0.89        47
     Crawfor       0.98      0.94      0.96        47
     Edwards       0.83      0.73      0.78        48
     Gilbert       0.90      0.96      0.93        47
       NAmes       0.79      0.72      0.76        47
      NWAmes       0.88      0.96      0.92        47
     NridgHt       0.92      0.98      0.95        47
     OldTown       0.96      0.94      0.95        47
      Sawyer       0.80      0.91      0.85        47
     SawyerW       0.98      0.96      0.97        48
     Somerst       0.98      1.00      0.99        47

   micro avg       0.91      0.91      0.91       566
   macro avg       0.91      0.91      0.91       566
weighted avg       0.91      0.91      0.91       566

Support Vector Machine

Adicionalmente podemos hacer Suppport Vector Machine, buscamos los mejores parámetros con GridSearchCV:

In [ ]:
param_gridSVM = {'C': [1, 10 , 100, 1000], 'gamma': [0.001,0.1, 1, 10], 'kernel': ['linear', 'rbf'],}
clf_gridSVM = GridSearchCV(SVC(), param_gridSVM, verbose=1, n_jobs=-1, cv=cv)
clf_gridSVM.fit(X2, y)
print("Mejores parametros SVM: ", clf_gridSVM.best_params_)
print("Score: ", clf_gridSVM.best_score_)
print("Mejores estimadores SVM: ", clf_gridSVM.best_estimator_)

Lo que arroja que los mejores parámetros serían $C=10$, $\gamma=0.1$ y $\texttt{kernel = 'rbf'}$ con un score del 0.889. Pasamos entonces a entrenar con un training set del 70% y obtenemos la matríz de confusión:

In [10]:
svm = SVC(C=10, gamma=0.1, kernel='rbf')
svm.fit(X_train, y_train)

y_predSVM = svm.predict(X_test)

conf_matrixSVM = confusion_matrix(y_test, y_predSVM)
print("Accuracy en test set:", accuracy_score(y_test, y_predSVM))

print("Reporte de Clasificacion:")
print(classification_report(y_test, y_predSVM))

classNames = barriosOK
f = print_confusion_matrix(conf_matrixSVM, classNames,  title='Confusion Matrix SVM')
plt.show()
Accuracy en test set: 0.8904593639575972
Reporte de Clasificacion:
              precision    recall  f1-score   support

     BrkSide       0.90      0.96      0.93        47
     CollgCr       0.95      0.87      0.91        47
     Crawfor       1.00      0.85      0.92        47
     Edwards       1.00      0.73      0.84        48
     Gilbert       0.92      1.00      0.96        47
       NAmes       0.88      0.74      0.80        47
      NWAmes       0.95      0.85      0.90        47
     NridgHt       0.98      0.96      0.97        47
     OldTown       0.55      1.00      0.71        47
      Sawyer       0.98      0.89      0.93        47
     SawyerW       0.95      0.85      0.90        48
     Somerst       0.98      0.98      0.98        47

   micro avg       0.89      0.89      0.89       566
   macro avg       0.92      0.89      0.90       566
weighted avg       0.92      0.89      0.90       566

De las dos matrices de confusión se puede ver Sawyer es mejor clasificado en SVM que en Random Forest, mientras que CollgCr y Edwards son mejores clasificados en Random Forest. En cuando a Gilbert se muestra una deficiencia de ambos algorítmos en su clasificación, esto podría ser porque en Gilbert podrían abundar viviendas muy distintas y considerando que se hizo un SMOTE de 79 a 225 viviendas se podría haber aumentado el Bias de este.

Nonnegative Matrix Factorization

In [36]:
from sklearn.decomposition import NMF
df = pd.read_csv('train.csv', index_col = 0)

Seleccionamos a mano atributos que consideramos generalizables a otros datasets y podrían determinar de mejor manera a distintos tipos de casas:

In [19]:
atributos = ["LotFrontage","LotArea", "OverallQual","OverallCond","MasVnrArea", "ExterQual","ExterQual", "BsmtQual","BsmtCond","BsmtExposure","BsmtFinType1","BsmtFinSF1","BsmtFinType2",
                 "BsmtFinSF2","TotalBsmtSF","BsmtUnfSF","HeatingQC","CentralAir","1stFlrSF","2ndFlrSF","LowQualFinSF",
                 "GrLivArea","BsmtFullBath","BsmtHalfBath","FullBath","HalfBath","BedroomAbvGr","KitchenAbvGr","KitchenQual",
                 "TotRmsAbvGrd","Fireplaces","FireplaceQu","GarageArea","GarageQual",
                 "GarageCond","WoodDeckSF","OpenPorchSF","EnclosedPorch","3SsnPorch","PoolArea", "ScreenPorch","PoolQC",
                 "MoSold","MiscVal", "SalePrice"] #
featureList = np.array(df.columns).tolist()
for feature in featureList:
    if df[feature].dtype == 'object':
        df[feature] = LabelEncoder().fit_transform(df[feature].tolist())        
imp = Imputer(missing_values=np.nan, strategy='mean')
df[featureList] = imp.fit_transform(df[featureList])
C:\Users\King\Anaconda3\lib\site-packages\sklearn\utils\deprecation.py:58: DeprecationWarning: Class Imputer is deprecated; Imputer was deprecated in version 0.20 and will be removed in 0.22. Import impute.SimpleImputer from sklearn instead.
  warnings.warn(msg, category=DeprecationWarning)
In [20]:
trainNMF = df.filter(atributos)

Tomamos la matriz transpuesta de trainNMF para entrenar de manera apropiada. Ahora, las filas corresponden a las casas y las columnas a los atributos:

In [21]:
trainNMF = trainNMF.transpose()
trainNMF.head()
Out[21]:
Id 1 2 3 4 5 6 7 8 9 10 ... 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460
LotFrontage 65.0 80.0 68.0 60.0 84.0 85.0 75.0 70.049958 51.0 50.0 ... 60.0 78.0 35.0 90.0 62.0 62.0 85.0 66.0 68.0 75.0
LotArea 8450.0 9600.0 11250.0 9550.0 14260.0 14115.0 10084.0 10382.000000 6120.0 7420.0 ... 9000.0 9262.0 3675.0 17217.0 7500.0 7917.0 13175.0 9042.0 9717.0 9937.0
OverallQual 7.0 6.0 7.0 7.0 8.0 5.0 8.0 7.000000 7.0 5.0 ... 5.0 8.0 5.0 5.0 7.0 6.0 6.0 7.0 5.0 5.0
OverallCond 5.0 8.0 5.0 5.0 5.0 5.0 5.0 6.000000 5.0 6.0 ... 5.0 5.0 5.0 5.0 5.0 5.0 6.0 9.0 6.0 6.0
MasVnrArea 196.0 0.0 162.0 0.0 350.0 0.0 186.0 240.000000 0.0 0.0 ... 0.0 194.0 80.0 0.0 0.0 0.0 119.0 0.0 0.0 0.0

5 rows × 1460 columns

Generamos estadísticas para distintos números de grupos de casas a generar:

In [24]:
for i in range(2,11):
    print("STATISTICS FOR ", i, "GROUPS")
    model = NMF(n_components = i, solver = 'cd' )
    W = model.fit_transform(trainNMF)
    H = model.components_
    for j in range(len(H)):
        print("The standard deviation of group ", j, "is: ", H[j].std())
        print("The mean of group ", j, "is: ", H[j].mean())
        print("The 75th percentile of group ",j,"is: ", np.percentile(H[j], 75))
        print("The min of this group is: ", H[j].min(), "and the max is: ", H[j].max())
        print("----------------------------------------------------------------")
STATISTICS FOR  2 GROUPS
The standard deviation of group  0 is:  28.918111554710478
The mean of group  0 is:  65.88408351364588
The 75th percentile of group  0 is:  77.93015123287076
The min of this group is:  12.711896695857078 and the max is:  274.9266976898035
----------------------------------------------------------------
The standard deviation of group  1 is:  12.23247886532198
The mean of group  1 is:  9.98160962353187
The 75th percentile of group  1 is:  11.021631394802053
The min of this group is:  0.0 and the max is:  263.61367979316515
----------------------------------------------------------------
STATISTICS FOR  3 GROUPS
The standard deviation of group  0 is:  28.912411664041908
The mean of group  0 is:  65.86696501777034
The 75th percentile of group  0 is:  77.90937838591927
The min of this group is:  12.705103834814372 and the max is:  274.87123669060685
----------------------------------------------------------------
The standard deviation of group  1 is:  12.031674484356646
The mean of group  1 is:  8.024695549840741
The 75th percentile of group  1 is:  9.132347001224332
The min of this group is:  0.0 and the max is:  258.8260570786895
----------------------------------------------------------------
The standard deviation of group  2 is:  1.3499985348123125
The mean of group  2 is:  2.2304619872875064
The 75th percentile of group  2 is:  2.903494027575423
The min of this group is:  0.0 and the max is:  19.086535360164135
----------------------------------------------------------------
STATISTICS FOR  4 GROUPS
The standard deviation of group  0 is:  28.905811422260975
The mean of group  0 is:  65.85085334387107
The 75th percentile of group  0 is:  77.8875363781097
The min of this group is:  12.703111858171159 and the max is:  274.8020116399965
----------------------------------------------------------------
The standard deviation of group  1 is:  11.57775223157096
The mean of group  1 is:  7.39604225955415
The 75th percentile of group  1 is:  8.332503326427048
The min of this group is:  0.0 and the max is:  250.71583808580107
----------------------------------------------------------------
The standard deviation of group  2 is:  1.4114453414467714
The mean of group  2 is:  1.8662493639529496
The 75th percentile of group  2 is:  2.6718488638866087
The min of this group is:  0.0 and the max is:  17.669382101541412
----------------------------------------------------------------
The standard deviation of group  3 is:  1.1093605784331788
The mean of group  3 is:  0.9474680011520794
The 75th percentile of group  3 is:  1.8854577344506236
The min of this group is:  0.0 and the max is:  4.852988105057251
----------------------------------------------------------------
STATISTICS FOR  5 GROUPS
The standard deviation of group  0 is:  28.905576317733843
The mean of group  0 is:  65.8503575211638
The 75th percentile of group  0 is:  77.88686167198534
The min of this group is:  12.703045002871479 and the max is:  274.7997495082754
----------------------------------------------------------------
The standard deviation of group  1 is:  11.571523291961535
The mean of group  1 is:  7.361340433383198
The 75th percentile of group  1 is:  8.302687376584355
The min of this group is:  0.0 and the max is:  250.7147342815865
----------------------------------------------------------------
The standard deviation of group  2 is:  1.425774720089534
The mean of group  2 is:  1.880844896234268
The 75th percentile of group  2 is:  2.6968281762792197
The min of this group is:  0.0 and the max is:  17.83211088905587
----------------------------------------------------------------
The standard deviation of group  3 is:  1.1385554928296633
The mean of group  3 is:  0.9715950795626519
The 75th percentile of group  3 is:  1.9413942700896485
The min of this group is:  0.0 and the max is:  5.000185798326677
----------------------------------------------------------------
The standard deviation of group  4 is:  3.60102053141623
The mean of group  4 is:  0.35078405934078133
The 75th percentile of group  4 is:  0.06685181236246743
The min of this group is:  0.0 and the max is:  112.57328738902432
----------------------------------------------------------------
STATISTICS FOR  6 GROUPS
The standard deviation of group  0 is:  28.908561054296978
The mean of group  0 is:  65.85237724160457
The 75th percentile of group  0 is:  77.89185834163251
The min of this group is:  12.701468708978835 and the max is:  274.8312006740616
----------------------------------------------------------------
The standard deviation of group  1 is:  11.376299650750678
The mean of group  1 is:  7.529980921196971
The 75th percentile of group  1 is:  8.790694714115546
The min of this group is:  0.0 and the max is:  246.63566244009263
----------------------------------------------------------------
The standard deviation of group  2 is:  1.6488492138540267
The mean of group  2 is:  1.6672313015420062
The 75th percentile of group  2 is:  2.654199115984257
The min of this group is:  0.0 and the max is:  20.985670860935098
----------------------------------------------------------------
The standard deviation of group  3 is:  1.218730038595149
The mean of group  3 is:  1.0458227214617506
The 75th percentile of group  3 is:  2.0606737336274863
The min of this group is:  0.0 and the max is:  5.215667149749314
----------------------------------------------------------------
The standard deviation of group  4 is:  4.29180073132816
The mean of group  4 is:  0.3832012367500461
The 75th percentile of group  4 is:  0.010423486624817586
The min of this group is:  0.0 and the max is:  134.12456703709964
----------------------------------------------------------------
The standard deviation of group  5 is:  2.7454539595217082
The mean of group  5 is:  3.664320088972489
The 75th percentile of group  5 is:  5.04287914460426
The min of this group is:  0.0 and the max is:  14.93366704909513
----------------------------------------------------------------
STATISTICS FOR  7 GROUPS
The standard deviation of group  0 is:  28.90884157359694
The mean of group  0 is:  65.85310056771833
The 75th percentile of group  0 is:  77.89149238364811
The min of this group is:  12.700905407656439 and the max is:  274.8364291077931
----------------------------------------------------------------
The standard deviation of group  1 is:  11.80345880761308
The mean of group  1 is:  6.9950259989999495
The 75th percentile of group  1 is:  8.292794418424274
The min of this group is:  0.0 and the max is:  256.2671942940419
----------------------------------------------------------------
The standard deviation of group  2 is:  1.525344267790141
The mean of group  2 is:  1.4786871852579735
The 75th percentile of group  2 is:  2.3941064968474914
The min of this group is:  0.0 and the max is:  19.334299010280965
----------------------------------------------------------------
The standard deviation of group  3 is:  1.1080423352492696
The mean of group  3 is:  0.8927334221098034
The 75th percentile of group  3 is:  1.8547736559747203
The min of this group is:  0.0 and the max is:  5.1779301520523235
----------------------------------------------------------------
The standard deviation of group  4 is:  4.3405977082334255
The mean of group  4 is:  0.3878076577331678
The 75th percentile of group  4 is:  0.011537991284578868
The min of this group is:  0.0 and the max is:  135.65202240232557
----------------------------------------------------------------
The standard deviation of group  5 is:  2.722566219306064
The mean of group  5 is:  3.398923399389564
The 75th percentile of group  5 is:  4.6475492496307576
The min of this group is:  0.0 and the max is:  14.68806505883624
----------------------------------------------------------------
The standard deviation of group  6 is:  2.8266576806936663
The mean of group  6 is:  3.2953877584723084
The 75th percentile of group  6 is:  4.367611936055173
The min of this group is:  0.0 and the max is:  19.12241256605721
----------------------------------------------------------------
STATISTICS FOR  8 GROUPS
The standard deviation of group  0 is:  28.906259799165987
The mean of group  0 is:  65.8492708155558
The 75th percentile of group  0 is:  77.88436570442346
The min of this group is:  12.703014220153316 and the max is:  274.8196289449104
----------------------------------------------------------------
The standard deviation of group  1 is:  12.04582343093147
The mean of group  1 is:  6.713492903227748
The 75th percentile of group  1 is:  7.895908407953912
The min of this group is:  0.0 and the max is:  257.68573651643186
----------------------------------------------------------------
The standard deviation of group  2 is:  2.0451273758640367
The mean of group  2 is:  1.808535579843782
The 75th percentile of group  2 is:  3.020720891625554
The min of this group is:  0.0 and the max is:  11.8093379068297
----------------------------------------------------------------
The standard deviation of group  3 is:  1.263559574889695
The mean of group  3 is:  0.9941687998603319
The 75th percentile of group  3 is:  2.093826277908141
The min of this group is:  0.0 and the max is:  6.08321705892171
----------------------------------------------------------------
The standard deviation of group  4 is:  4.3306387767406065
The mean of group  4 is:  0.3854333913774589
The 75th percentile of group  4 is:  0.010426384703451736
The min of this group is:  0.0 and the max is:  135.34310787694898
----------------------------------------------------------------
The standard deviation of group  5 is:  2.1720571104795474
The mean of group  5 is:  2.355081053717266
The 75th percentile of group  5 is:  3.4496307934343524
The min of this group is:  0.0 and the max is:  12.402155955097083
----------------------------------------------------------------
The standard deviation of group  6 is:  1.8669882775042672
The mean of group  6 is:  1.4499085551107969
The 75th percentile of group  6 is:  1.8522933534021722
The min of this group is:  0.0 and the max is:  14.420586696225504
----------------------------------------------------------------
The standard deviation of group  7 is:  2.0318250692421045
The mean of group  7 is:  2.02596686508142
The 75th percentile of group  7 is:  3.2034824341414003
The min of this group is:  0.0 and the max is:  25.09418477563565
----------------------------------------------------------------
STATISTICS FOR  9 GROUPS
The standard deviation of group  0 is:  28.904330979285945
The mean of group  0 is:  65.8467147610657
The 75th percentile of group  0 is:  77.88287694973414
The min of this group is:  12.702026148974381 and the max is:  274.79336772092665
----------------------------------------------------------------
The standard deviation of group  1 is:  12.127547559925173
The mean of group  1 is:  6.608068617014364
The 75th percentile of group  1 is:  7.804993573981978
The min of this group is:  0.0 and the max is:  260.3931156524655
----------------------------------------------------------------
The standard deviation of group  2 is:  2.072200441777487
The mean of group  2 is:  1.7208999756013394
The 75th percentile of group  2 is:  2.855356111051092
The min of this group is:  0.0 and the max is:  11.738076610411115
----------------------------------------------------------------
The standard deviation of group  3 is:  1.336930760418552
The mean of group  3 is:  1.0669476479026543
The 75th percentile of group  3 is:  2.280002458440768
The min of this group is:  0.0 and the max is:  7.049966135490471
----------------------------------------------------------------
The standard deviation of group  4 is:  4.319120152963911
The mean of group  4 is:  0.38309746186654675
The 75th percentile of group  4 is:  0.008133162544366343
The min of this group is:  0.0 and the max is:  134.98311354425516
----------------------------------------------------------------
The standard deviation of group  5 is:  2.0471333486083925
The mean of group  5 is:  2.265604515573117
The 75th percentile of group  5 is:  3.333121611505373
The min of this group is:  0.0 and the max is:  10.673324942965541
----------------------------------------------------------------
The standard deviation of group  6 is:  1.8712438798252071
The mean of group  6 is:  1.5604013344606567
The 75th percentile of group  6 is:  1.949023465821052
The min of this group is:  0.0 and the max is:  13.274763032076224
----------------------------------------------------------------
The standard deviation of group  7 is:  1.8727243999852254
The mean of group  7 is:  1.8646867637334397
The 75th percentile of group  7 is:  2.9497376140365046
The min of this group is:  0.0 and the max is:  23.029143920042216
----------------------------------------------------------------
The standard deviation of group  8 is:  1.8201710990774862
The mean of group  8 is:  1.051195864636568
The 75th percentile of group  8 is:  1.44470504290175
The min of this group is:  0.0 and the max is:  17.039527667351702
----------------------------------------------------------------
STATISTICS FOR  10 GROUPS
The standard deviation of group  0 is:  28.894762088708916
The mean of group  0 is:  65.8365191099055
The 75th percentile of group  0 is:  77.87685295968433
The min of this group is:  12.702116248301097 and the max is:  274.5608683845854
----------------------------------------------------------------
The standard deviation of group  1 is:  12.372366922858825
The mean of group  1 is:  6.259572121766486
The 75th percentile of group  1 is:  7.364789446617194
The min of this group is:  0.0 and the max is:  266.7017167080967
----------------------------------------------------------------
The standard deviation of group  2 is:  2.0663649440005947
The mean of group  2 is:  1.7501427236995197
The 75th percentile of group  2 is:  2.9474449208539375
The min of this group is:  0.0 and the max is:  12.151485601481578
----------------------------------------------------------------
The standard deviation of group  3 is:  1.9405738958023961
The mean of group  3 is:  1.4479353775582866
The 75th percentile of group  3 is:  3.102924847460644
The min of this group is:  0.0 and the max is:  9.604477160236069
----------------------------------------------------------------
The standard deviation of group  4 is:  4.254564768496508
The mean of group  4 is:  0.374706580090939
The 75th percentile of group  4 is:  0.002544144301532311
The min of this group is:  0.0 and the max is:  132.96406763986963
----------------------------------------------------------------
The standard deviation of group  5 is:  1.8680228433296187
The mean of group  5 is:  2.1273192579847877
The 75th percentile of group  5 is:  3.145283191495558
The min of this group is:  0.0 and the max is:  10.173154454353618
----------------------------------------------------------------
The standard deviation of group  6 is:  1.604668208943381
The mean of group  6 is:  1.1986014915825465
The 75th percentile of group  6 is:  1.5259146908300008
The min of this group is:  0.0 and the max is:  11.760267402598249
----------------------------------------------------------------
The standard deviation of group  7 is:  1.9821910742692077
The mean of group  7 is:  1.9715045659387371
The 75th percentile of group  7 is:  3.1344161149485372
The min of this group is:  0.0 and the max is:  24.43715838280757
----------------------------------------------------------------
The standard deviation of group  8 is:  0.7704432766248843
The mean of group  8 is:  1.7052551540651053
The 75th percentile of group  8 is:  2.0793960585310347
The min of this group is:  0.0 and the max is:  5.040586789726267
----------------------------------------------------------------
The standard deviation of group  9 is:  1.9360309365345292
The mean of group  9 is:  0.8946938088145603
The 75th percentile of group  9 is:  0.7003339945528535
The min of this group is:  0.0 and the max is:  16.609534137118384
----------------------------------------------------------------

Escogemos trabajar con 7 grupos de casas distintos:

In [25]:
model = NMF(n_components = 7, solver = 'cd' )
W = model.fit_transform(trainNMF)
H = model.components_

A partir del percentil de los valores en cada grupo de la matriz H, determinamos de manera arbitraria que si una casa posee un valor mayor al percentil 75 dentro de un grupo, entonces esa casa pertenece a tal grupo. De esta manera, no pueden quedar grupos vacíos ya que se determina la pertenencia en función al percentil determinado por las mismas casas, y una casa puede pertenecer a uno o más grupos dependiendo de su peso en cada uno de los grupos.

In [26]:
clusterized_houses = [[] for _ in range(len(H))]
for i in range(len(H)):
    threshold = np.percentile(H[i], 75)
#     print(threshold)
    for j in range(1460):
        if H[i][j] >= threshold:
            clusterized_houses[i].append(1)
        else:
            clusterized_houses[i].append(0)
In [29]:
for i in range(len(H)):
    n = sum(element > 0 for element in H[i])
    print("La cantidad de casas en el grupo ",i,"es: ",n)
La cantidad de casas en el grupo  0 es:  1460
La cantidad de casas en el grupo  1 es:  1346
La cantidad de casas en el grupo  2 es:  994
La cantidad de casas en el grupo  3 es:  1157
La cantidad de casas en el grupo  4 es:  1169
La cantidad de casas en el grupo  5 es:  1354
La cantidad de casas en el grupo  6 es:  1363

¿Cómo podemos asignar una característica de una casa a uno de estos 7 grupos? Intuitivamente, si una característica de casa tiene un peso largo en un tipo de casa, se debería asignar esta característica a ese tipo de casa. Por ejemplo, si en el grupo 1 (vale decir, tipo de casa 1) el peso de OverallCond (condición general de la casa) es muy grande, deberíamos asignar esta característica como perteneciente a este grupo/cluster de tipos de casas.

Una misma característica puede pertenecer a más de un grupo, lo cual es esperable dado que por muy distintas que sean las casas una que otra característica van a tener en común. Bajo esta estrategia de clustering, cada característica de casa se asigna a múltiples clústeres o ningún grupo. Para determinar qué es muy grande podemos ocupar el p-percentile como umbral (si una característica tiene un peso mayor al umbral en un cierto grupo, la asignamos a un grupo).

Luego, cada uno de los 7 grupos que generamos de manera arbitraria está determinado por las características que lo componen. E.g., grupo 1 = {OpenPorchSF, LotFrontage,...,SalePrice}, grupo 2 = {FirePlaces, HeatingQC,...,LotArea}, etcétera.

Para cada uno de los 7 grupos generados, podemos escoger las 10 características más importantes que lo definen. La idea detrás de esto es que si de alguna manera podemos encontrar una relación entre grupos de casas definidos por expertos y los grupos de casas generados por nuestro algoritmo, luego un asesor que esté interesado en vender una casa puede estimar qué tipo de casa está viendo y gracias a la relación encontrada, determinar las 10 características más relevantes para esa casa en particular, y así dar alguna recomendación a las personas interesadas en la transacción.

In [35]:
tr = W.transpose()
for i in range(len(tr)):
    aux = []
    for element in tr[i].argsort()[-10:]:
        aux.append(atributos[element])
    print("Las 10 características más relevantes para el grupo ", i+1, "son: ",aux)
Las 10 características más relevantes para el grupo  1 son:  ['OpenPorchSF', 'LotFrontage', 'WoodDeckSF', 'TotalBsmtSF', 'MasVnrArea', 'GarageArea', '1stFlrSF', 'GrLivArea', 'LotArea', 'SalePrice']
Las 10 características más relevantes para el grupo  2 son:  ['Fireplaces', 'HeatingQC', '3SsnPorch', 'LotFrontage', 'SalePrice', 'BsmtFinSF2', 'WoodDeckSF', 'BsmtUnfSF', 'TotalBsmtSF', 'LotArea']
Las 10 características más relevantes para el grupo  3 son:  ['OpenPorchSF', 'LotFrontage', 'WoodDeckSF', 'MasVnrArea', 'GarageArea', 'GrLivArea', '1stFlrSF', 'TotalBsmtSF', 'BsmtFinSF1', 'LotArea']
Las 10 características más relevantes para el grupo  4 son:  ['EnclosedPorch', 'LotFrontage', 'BsmtFinSF1', 'OpenPorchSF', 'GarageArea', 'TotalBsmtSF', 'BsmtUnfSF', 'GrLivArea', '2ndFlrSF', 'LotArea']
Las 10 características más relevantes para el grupo  5 son:  ['FireplaceQu', 'OverallCond', 'TotalBsmtSF', 'LotFrontage', 'PoolArea', 'EnclosedPorch', 'SalePrice', 'ScreenPorch', 'LotArea', 'MiscVal']
Las 10 características más relevantes para el grupo  6 son:  ['MoSold', 'EnclosedPorch', 'OpenPorchSF', 'LotFrontage', 'SalePrice', 'GarageArea', 'GrLivArea', '1stFlrSF', 'TotalBsmtSF', 'BsmtUnfSF']
Las 10 características más relevantes para el grupo  7 son:  ['ScreenPorch', 'LowQualFinSF', 'EnclosedPorch', 'LotFrontage', 'TotalBsmtSF', 'GarageArea', 'BsmtFinSF2', 'GrLivArea', '1stFlrSF', 'LotArea']

Conclusiones

Luego de realizar los experimentos, podemos mencionar en relación a nuestro objetivo principal, predecir el precio de venta de una casa a partir de sus atributos, que Gradient Boostin Regression entrega mejores resultados (GBR R^2 Score: 0.903) que Random Forest Regression (R^2 Score: 0.869). Al reducir la dimensionalidad eliminando features estas métricas se mantuvieron relativamente constante (score R^2 CBR = 0.896 vs score R^2 RFR = 0.868), mientras que el tiempo de entrenamiento se redujo en alrededor de un 30%, presentandose así una buena opción para entrenar con recursos limitados y/o mayor cantidad de datos, al mismo tiempo habla sobre la redundancia de más de la mitad de los parámetros, ya que las columnas utilizadas bajaron de 74 a 34 sin perjudicar el desempeño de los algoritmos.

Otro punto relevante es que nuestro análisis de casas por barrios es débil y el motivo de esto fué ignorado hasta muy tarde en el desarrollo de nuestro proyecto. La forma en la que predecimos precio por casa en función del barrio es la misma que ocupamos para predecir precio de casas a partir de sus atributos en general, sólo que en vez de ocupar el dataset completo se reducen las filas a cantidades muy pequeñas, lo cual no presenta mayor interés ni relevancia de acuerdo a lo que esperamos producir y obtener. A partir de esto creemos que es interesante entrenar un clasificador que a partir de las características de una casa, incluyendo su precio de venta, pueda predecir el barrio al cual pertenece dicha casa. Este objetivo si bien no es generalizable, puesto que tiene validez sólo dentro de la localidad donde se encuentran las casas utilizadas para entrenar a los clasificadores, despierta nuestro interés y creemos podría utilizarse en una localidad dada para estudiar su entorno inmobiliario.

El porcentaje de predicción de barrios a partir de las características de las casas es muy alto, tanto para SVM como Ranfom Forest. Algunos barrios presentan mejores métricas con SVM y otros con Random Forest, mientras que otros barrios como Gilbert no resultan tan bien clasificados, lo que se podría deber a la variedad de viviendas en Gilbert u otra cosa. Queda pendiente revisitar estas métricas ya que como fué comentado en la presentación, son valores de predicción demasiado altos.

En cuanto a la clasificación de casas en grupos de casas, los resultados expuestos muestran que es posible realizar este análisis. Queda pendiente revisitar el problema con distintos parámetros a los 45 escogidos, y consultar con algún experto para validar la utilidad de los grupos generados, así como también medir la distancia entre clusters generados para obtener un número óptimo de clusters, ya que en el análisis presentado en este informe se escogieron de manera arbitraria "al ojo". Una vez determinada una relación entre grupos de casas establecidos por la sociedad y los grupos de casas generados por nuestro algoritmo, es posible obtener las características más relevantes para cada tipo de casa y así permitir a la gente enfocarse más en estos ya sea tanto para vender o comprar.

Finalmente, es importante mencionar la relevancia de las técnicas adquiridas durante el curso para poder realizar un proyecto de minería de datos desde cero. Efectivamente la mayor parte del tiempo se ocupa en entender cómo trabajar con los datos, qué normalizar, qué variables construir, qué eliminar y qué mantener, cómo transformar los datos a algún formato apropiado para análisis, etcétera, todo esto y más se aplicó para el desarrollo de este proyecto y de seguro se volverá a aplicar cuando sea necesario.

Bibliografía

  1. https://en.wikipedia.org/wiki/Real_estate_appraisal
  2. https://www.kaggle.com/c/house-prices-advanced-regression-techniques
  3. https://scikit-learn.org/stable/modules/classes.html
  4. http://mlexplained.com/2017/12/28/a-practical-introduction-to-nmf-nonnegative-matrix-factorization/
  5. https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.NMF.html